import wx
from wx.glcanvas import GLCanvas
from OpenGL.GLU import *
from OpenGL.GL import *
import numpy as np
from threading import Thread
import math

import brushtools
from brushtools import AllTools
from ffttexture import *

# This is the editor.
class WaveformPanel(GLCanvas):
    def __init__(self, parent, id, project, size = (-1,-1)):
        GLCanvas.__init__(self, parent, id, size, attribList=[wx.glcanvas.WX_GL_RGBA, wx.glcanvas.WX_GL_DOUBLEBUFFER])
        self.project = project

        # This allows using the buffered device context. Without the buffered version,
        # the screen flickers while resizing.
        self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)

        # The sample that the mouse cursor is located at
        self.cursor = 0

        # "Radius" because "size" isn't descriptive. Unit is samples
        self.brushRadius = 1024
        self.featherRadius = 1024
        self.featherRatio = 1.0
        self.brushMap = brushtools.GetBrushMap(self.brushRadius, self.featherRadius)

        # The intensity of the brush for each click / mouse motion
        self.fade = 0.03

        # Set when mouse down, unset when mouse up
        self.clicking = False

        # Set for right click mouse down
        self.rightClicking = False
        self.rightClickStartX = 0
        self.rightClickStartBrush = 0
        self.rightClickStartY = 0
        self.rightClickStartFeather = 0

        self.Bind(wx.EVT_PAINT, self.OnPaint)
        self.Bind(wx.EVT_SIZE, self.OnResize)
        self.Bind(wx.EVT_MOTION, self.OnMouseMotion)
        self.Bind(wx.EVT_LEFT_DOWN, self.OnMouseLeftDown)
        self.Bind(wx.EVT_LEFT_UP, self.OnMouseLeftUp)
        self.Bind(wx.EVT_RIGHT_DOWN, self.OnMouseRightDown)
        self.Bind(wx.EVT_RIGHT_UP, self.OnMouseRightUp)
        self.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel)
        self.Bind(wx.EVT_ENTER_WINDOW, self.OnMouseEnter)

        self.glinit = False
        self.fft = FFTTexture(512)

    # Convert from x coordinate on the frame to a sample index
    def XToSamples(self, x, ignoreOffset = False):
        sampleWidth = self.project.sampleRange[1] - self.project.sampleRange[0]
        size = self.GetSize()
        if ignoreOffset:
            return int(x * sampleWidth / float(size[0]))
        return int(x * sampleWidth / float(size[0]) + self.project.sampleRange[0])

    # Convert from a sample index to an x coordinate on the frame
    def SamplesToX(self, s, ignoreOffset = False):
        sampleWidth = self.project.sampleRange[1] - self.project.sampleRange[0]
        size = self.GetSize()
        if ignoreOffset:
            return s * float(size[0]) / sampleWidth
        return (s - self.project.sampleRange[0]) * float(size[0]) / sampleWidth

    # Applies the current brush to the cursor region
    def Apply(self):
        start = self.cursor - self.brushRadius
        end = self.cursor + self.brushRadius

        for i in range(len(self.project.sample)):
            self.project.currentTool.ApplyThreaded(self.project.originalSample[i], self.project.sample[i], start, end, self.featherRadius, self.fade, self.brushMap, i)

        self.oldFFT = True

    def OnMouseEnter(self, evt):
        self.clicking = evt.LeftIsDown()

    def OnMouseWheel(self, evt):
        size = self.GetSize()
        sampleLen = len(self.project.sample[0])
        if evt.GetWheelRotation() > 0:
            zoom = 0.5
        else:
            zoom = 2.0
        sr = self.project.sampleRange
        curRange = sr[1] - sr[0]
        curRange = max(min(curRange * zoom, sampleLen), 128)
        half = curRange / 2
        sMid = self.XToSamples(evt.GetX())
        offset = self.XToSamples(evt.GetX() - size[0]/2, ignoreOffset = True)
        sMid -= offset * zoom
        if sMid - half < 0:
            sMid = half
        elif sMid + half > sampleLen:
            sMid = sampleLen - half
        self.project.sampleRange[0] = int(sMid - half)
        self.project.sampleRange[1] = int(sMid + half)
        self.cursor = self.XToSamples(evt.GetX())
        self.oldFFT = True

    def OnMouseRightDown(self, evt):
        self.rightClickStartX = evt.GetX()
        self.rightClickStartY = evt.GetY()
        self.rightClickStartBrush = self.brushRadius
        self.rightClickStartFeather = self.featherRadius
        self.rightClicking = True

    def OnMouseRightUp(self, evt):
        self.rightClicking = False
        self.brushRadius = min(self.brushRadius, 22050)
        self.featherRadius = int(self.brushRadius * self.featherRatio)
        self.brushMap = brushtools.GetBrushMap(self.brushRadius, self.featherRadius)

    def OnMouseLeftUp(self, evt):
        self.clicking = False
        evt.Skip()

    def OnMouseLeftDown(self, evt):
        self.Apply()
        self.clicking = True
        evt.Skip()

    def OnMouseMotion(self, evt):
        if self.rightClicking:
            self.brushRadius = self.rightClickStartBrush + self.XToSamples(evt.GetX() - self.rightClickStartX, ignoreOffset = True)
            self.brushRadius = max(self.brushRadius, 0)
            self.featherRadius = int(self.brushRadius * self.featherRatio)
        else:
            self.cursor = self.XToSamples(evt.GetX())
        # Apply the brush every mouse motion, like in photoshop
        if self.clicking:
            self.Apply()
        evt.Skip()

    def Init(self):
        size = self.GetClientSize()
        glEnable(GL_BLEND)
        glEnable(GL_LINE_SMOOTH)
        glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA)
        glViewport(0, 0, size[0], size[1])

        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()
        glOrtho(0, size[0], size[1], 0, -1, 1)

        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()

    def OnResize(self, evt):
        self.Refresh()
        self.Init()
        evt.Skip()

    def DrawWaveformChannel(self, channel, originalChannel, height):
        size = self.GetSize()
        sampleWidth = self.project.sampleRange[1] - self.project.sampleRange[0]

        step = max(int(float(sampleWidth) / size[0]) / 4, 1)

        subSample = channel[self.project.sampleRange[0]:self.project.sampleRange[1]]
        subOriginalSample = originalChannel[self.project.sampleRange[0]:self.project.sampleRange[1]]
        subSample = subSample[::step]
        subOriginalSample = subOriginalSample[::step]
        steppedSampleWidth = len(subSample)

        samples = np.add(np.multiply(subSample, height / 2),height / 2)
        samples = samples.astype(np.float64)

        points = np.dstack( ( np.multiply(np.arange(0, sampleWidth, step), 1.0 / sampleWidth * size[0]), samples ))[0]
        redChannel = np.ones( (steppedSampleWidth, ) )
        greenChannel = np.ones( (steppedSampleWidth, ) )
        greenChannel = np.subtract(1, np.absolute(np.subtract( subSample, subOriginalSample)))
        averageWindow = 32
        #greenChannel = np.convolve(greenChannel, np.repeat(1.0 / averageWindow, averageWindow), mode="same")
        blueChannel = np.ones( (steppedSampleWidth, ) )
        alphaChannel = np.repeat( 0.5, steppedSampleWidth)
        colors = np.dstack((redChannel, greenChannel, blueChannel, alphaChannel ))[0]

        glEnableClientState(GL_VERTEX_ARRAY)
        glEnableClientState(GL_COLOR_ARRAY)
        glVertexPointerd(points)
        glColorPointerd(colors)

        glDrawArrays(GL_LINE_STRIP, 0, len(points))
        glDisableClientState(GL_VERTEX_ARRAY)
        glDisableClientState(GL_COLOR_ARRAY)

        cx = self.SamplesToX(self.cursor)
        bdx = self.SamplesToX(self.brushRadius, ignoreOffset = True)
        fdx = self.SamplesToX(self.featherRadius, ignoreOffset = True)

        glBegin(GL_QUADS)
        #Brush inside region
        glColor(0,1,0,0.5)
        glVertex(cx - bdx, height, 0)
        glVertex(cx - bdx, 0, 0)
        glVertex(cx + bdx, 0, 0)
        glVertex(cx + bdx, height, 0)
        #Brush feather left
        glColor(0,1,0,0)
        glVertex(cx - bdx - fdx, height, 0)
        glVertex(cx - bdx - fdx, 0, 0)
        glColor(0,1,0,0.5)
        glVertex(cx - bdx, 0, 0)
        glVertex(cx - bdx, height, 0)
        #Brush feather right
        glColor(0,1,0,0.5)
        glVertex(cx + bdx, height, 0)
        glVertex(cx + bdx, 0, 0)
        glColor(0,1,0,0)
        glVertex(cx + bdx + fdx, 0, 0)
        glVertex(cx + bdx + fdx, height, 0)
        glEnd()


        glBegin(GL_LINES)
        #Brush edges
        glColor(1, 1, 1, 1)
        glVertex(cx - bdx, 0, 0)
        glVertex(cx - bdx, height, 0)
        glVertex(cx + bdx, 0, 0)
        glVertex(cx + bdx, height, 0)

        # Playback bar
        glColor(1, 0, 0, 1)
        px = self.SamplesToX(self.project.samplePosition)
        glVertex(px, 0, 0)
        glVertex(px, height, 0)
        glEnd()

    def OnPaint(self, evt):
        self.SetCurrent()
        if not self.fft.calculating and self.project.spectrogram:
            self.fft.CalculateThreaded( self.project.sample[0][self.project.sampleRange[0]:self.project.sampleRange[1]] )
        if not self.glinit:
            self.Init()
            self.glinit = True
        glClearColor(0, 0, 0, 0)
        glClear(GL_COLOR_BUFFER_BIT)
        glPushMatrix()

        size = self.GetSize()
        parts = len(self.project.sample)
        if self.project.spectrogram:
            parts += 1
        size[1] /= 1.0 * parts
        y = 0
        for i in range(len(self.project.sample)):
            self.DrawWaveformChannel(self.project.sample[i], self.project.originalSample[i], size[1])
            glTranslate(0, size[1], 0)
            y += size[1]

        if self.project.spectrogram:
            tex = self.fft.GetTexture()
            if tex:
              glColor(1,1,1,1)
  
              glBegin(GL_LINES)
              glVertex(0, size[1], 0)
              glVertex(size[0], size[1], 0)
              glEnd()
  
              glEnable(GL_TEXTURE_2D)
              glEnable(GL_BLEND)
              glBlendFunc(GL_SRC_ALPHA, GL_ONE)
              glBindTexture(GL_TEXTURE_2D, tex)
              glBegin(GL_QUADS)
              glTexCoord2f(0,0)
              glVertex(0, 0, 0)
              glTexCoord2f(1, 0)
              glVertex(size[0], 0, 0)
              glTexCoord2f(1, 1)
              glVertex(size[0], size[1], 0)
              glTexCoord2f(0, 1)
              glVertex(0, size[1], 0)
              glEnd()
              glDisable(GL_TEXTURE_2D)
              glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)

        glFlush()
        glPopMatrix()
        self.SwapBuffers()
        self.Refresh()
